/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package jsr203.sun.nio.fs;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import jsr203.nio.file.FileRef;
import jsr203.nio.file.FileSystemException;
import jsr203.nio.file.ProviderMismatchException;
import jsr203.nio.file.attribute.NamedAttributeView;
import static jsr203.sun.nio.fs.LinuxNativeDispatcher.fgetxattr;
import static jsr203.sun.nio.fs.LinuxNativeDispatcher.flistxattr;
import static jsr203.sun.nio.fs.LinuxNativeDispatcher.fremovexattr;
import static jsr203.sun.nio.fs.LinuxNativeDispatcher.fsetxattr;
import static jsr203.sun.nio.fs.UnixConstants.ERANGE;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.close;
import sun.misc.Unsafe;

/**
 * Linux implementation of NamedAttributeView using extended attributes.
 */

class LinuxNamedAttributeView
    extends AbstractNamedAttributeView
{
    private static final Unsafe unsafe = CopyMemory.getUnsafe();

    // Parses buffer as array of NULL-terminated C strings.
    private List<String> asList(long address, int size) {
        final List<String> list = new ArrayList<String>();
        int start = 0;
        int pos = 0;
        while (pos < size) {
            if (unsafe.getByte(address + pos) == 0) {
                int len = pos - start;
                byte[] value = new byte[len];
                CopyMemory.toByteArray(address+start, value, len);
                // unsafe.copyMemory(null, address+start, value,
                //    Unsafe.ARRAY_BYTE_BASE_OFFSET, len);
                list.add(new String(value));
                start = pos + 1;
            }
            pos++;
        }
        return list;
    }

    @Override
    public NamedAttributeView bind(FileRef file, boolean followLinks) {
        if (file != null && !(file instanceof UnixPath))
            throw new ProviderMismatchException();
        setFileInfo(file, followLinks);
        return this;
    }

    @Override
    public Iterable<String> list() throws IOException  {
        final FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();
        if (System.getSecurityManager() != null)
            checkAccess(file.getPathForPermissionCheck(), true, false);

        int fd = file.openForAttributeAccess(info.followLinks());
        NativeBuffer buffer = null;
        try {
            int size = 1024;
            buffer = NativeBuffers.getNativeBuffer(size);
            for (;;) {
                try {
                    int n = flistxattr(fd, buffer.address(), size);
                    final List<String> list = asList(buffer.address(), n);
                    return new Iterable<String>() {
                        @Override
                        public Iterator<String> iterator() {
                            return newIterator(info, list);
                        }
                    };
                } catch (UnixException x) {
                    // allocate larger buffer if required
                    if (x.errno() == ERANGE && size < 32*1024) {
                        buffer.release();
                        size *= 2;
                        buffer = null;
                        buffer = NativeBuffers.getNativeBuffer(size);
                        continue;
                    }
                    throw new FileSystemException(file.getPathForExecptionMessage(),
                        null, "Unable to get list of named attributes: " +
                        x.getMessage());
                }
            }
        } finally {
            if (buffer != null)
                buffer.release();
            close(fd);
        }
    }

    @Override
    public int size(String name) throws IOException  {
        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();
        if (System.getSecurityManager() != null)
            checkAccess(file.getPathForPermissionCheck(), true, false);

        int fd = file.openForAttributeAccess(info.followLinks());
        try {
            // fgetxattr returns size if called with size==0
            return fgetxattr(fd, name.getBytes(), 0L, 0);
        } catch (UnixException x) {
            throw new FileSystemException(file.getPathForExecptionMessage(),
                null, "Unable to get size of named attribute '" + name +
                "': " + x.getMessage());
        } finally {
            close(fd);
        }
    }

    @Override
    public int read(String name, ByteBuffer dst) throws IOException {
        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();
        if (System.getSecurityManager() != null)
            checkAccess(file.getPathForPermissionCheck(), true, false);

        int fd = file.openForAttributeAccess(info.followLinks());
        int rem = dst.remaining();
        int pos = dst.position();
        NativeBuffer nb;
        long address;
        if (dst instanceof sun.nio.ch.DirectBuffer) {
            nb = null;
            address = ((sun.nio.ch.DirectBuffer)dst).address() + pos;
        } else {
            // substitute with native buffer
            nb = NativeBuffers.getNativeBuffer(rem);
            address = nb.address();
        }
        try {
            int n = fgetxattr(fd, name.getBytes(), address, rem);
            if (nb != null) {
                // copy from buffer into backing array
                // int off = dst.arrayOffset() + pos + Unsafe.ARRAY_BYTE_BASE_OFFSET;
                // unsafe.copyMemory(null, address, dst.array(), off, n);
                CopyMemory.toByteBuffer(address, dst, pos, n);
            }
            dst.position(pos + n);
            return n;
        } catch (UnixException x) {
            String msg = (x.errno() == ERANGE) ?
                "Insufficient space in buffer" : x.getMessage();
            throw new FileSystemException(file.getPathForExecptionMessage(),
                null, "Error reading named attribute '" + name + "': " + msg);
        } finally {
            if (nb != null)
                nb.release();
            close(fd);
        }
    }

    @Override
    public int write(String name, ByteBuffer src) throws IOException {
        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();
        if (System.getSecurityManager() != null)
            checkAccess(file.getPathForPermissionCheck(), false, true);

        int fd = file.openForAttributeAccess(info.followLinks());

        int rem = src.remaining();
        int pos = src.position();
        NativeBuffer nb;
        long address;
        if (src instanceof sun.nio.ch.DirectBuffer) {
            nb = null;
            address = ((sun.nio.ch.DirectBuffer)src).address() + pos;
        } else {
            // substitute with native buffer
            nb = NativeBuffers.getNativeBuffer(rem);
            address = nb.address();

            // copy from backing array into buffer
            // int off = src.arrayOffset() + pos + Unsafe.ARRAY_BYTE_BASE_OFFSET;
            // unsafe.copyMemory(src.array(), off, null, address, rem);
            CopyMemory.fromByteBuffer(src, pos, address, rem);
        }
        try {
            fsetxattr(fd, name.getBytes(), address, rem);
            src.position(pos + rem);
            return rem;
        } catch (UnixException x) {
            throw new FileSystemException(file.getPathForExecptionMessage(),
                null, "Error writing named attribute '" + name + "': " +
                x.getMessage());
        } finally {
            if (nb != null)
                nb.release();
            close(fd);
        }
    }

    @Override
    void implDelete(FileInfo info, String name) throws IOException {
        UnixPath file = (UnixPath)info.file();
        if (System.getSecurityManager() != null)
            checkAccess(file.getPathForPermissionCheck(), false, true);

        int fd = file.openForAttributeAccess(info.followLinks());
        try {
            fremovexattr(fd, name.getBytes());
        } catch (UnixException x) {
            throw new FileSystemException(file.getPathForExecptionMessage(),
                null, "Unable to delete named attribute '" + name + "': " + x.getMessage());
        } finally {
            close(fd);
        }
    }

    /**
     * Used by copyTo/moveTo to copy extended attributes from source to target.
     *
     * @param   ofd
     *          file descriptor for source file
     * @param   nfd
     *          file descriptor for target file
     */
    static void copyExtendedAttributes(int ofd, int nfd) {
        NativeBuffer buffer = null;
        try {

            // call flistxattr to get list of extended attributes.
            int size = 1024;
            buffer = NativeBuffers.getNativeBuffer(size);
            for (;;) {
                try {
                    size = flistxattr(ofd, buffer.address(), size);
                    break;
                } catch (UnixException x) {
                    // allocate larger buffer if required
                    if (x.errno() == ERANGE && size < 32*1024) {
                        buffer.release();
                        size *= 2;
                        buffer = null;
                        buffer = NativeBuffers.getNativeBuffer(size);
                        continue;
                    }

                    // unable to get list of attributes
                    return;
                }
            }

            // parse buffer as array of NULL-terminated C strings.
            long address = buffer.address();
            int start = 0;
            int pos = 0;
            while (pos < size) {
                if (unsafe.getByte(address + pos) == 0) {
                    // extract attribute name and copy attribute to target.
                    // FIXME: We can avoid needless copying by using address+pos
                    // as the address of the name.
                    int len = pos - start;
                    byte[] name = new byte[len];
                    CopyMemory.toByteArray(address+start, name, len);
                    // unsafe.copyMemory(null, address+start, name,
                    //     Unsafe.ARRAY_BYTE_BASE_OFFSET, len);
                    try {
                        copyExtendedAttribute(ofd, name, nfd);
                    } catch (UnixException ignore) {
                        // ignore
                    }
                    start = pos + 1;
                }
                pos++;
            }

        } finally {
            if (buffer != null)
                buffer.release();
        }
    }

    private static void copyExtendedAttribute(int ofd, byte[] name, int nfd)
        throws UnixException
    {
        int size = fgetxattr(ofd, name, 0L, 0);
        NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
        try {
            long address = buffer.address();
            size = fgetxattr(ofd, name, address, size);
            fsetxattr(nfd, name, address, size);
        } finally {
            buffer.release();
        }
    }
}
